package utils

import (
	"bytes"
	"crypto/hmac"
	"crypto/md5"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/base64"
	"encoding/gob"
	"encoding/hex"
	"fmt"
	"net"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"unicode"

	jsoniter "github.com/json-iterator/go"
)

var json = jsoniter.ConfigCompatibleWithStandardLibrary

//GetBinPath 获取当前程序完整路径
func GetBinPath() (string, error) {
	file, _ := exec.LookPath(os.Args[0])
	absBinPath, _ := filepath.Abs(file)
	binPath, err := filepath.EvalSymlinks(absBinPath)
	if nil != err {
		return "", err
	}
	return binPath, nil
}

//GetBinDir 获取当前程序所在目录
func GetBinDir() string {
	binPath, _ := GetBinPath()
	return filepath.Dir(binPath)
}

//RegGob *
func RegGob(o ...interface{}) {
	for _, v := range o {
		gob.Register(v)
	}
	mapGob := make(map[string]interface{})
	gob.Register(mapGob)
}

//GobMarshal *
func GobMarshal(v interface{}) ([]byte, error) {
	b := new(bytes.Buffer)
	err := gob.NewEncoder(b).Encode(v)
	if err != nil {
		return nil, err
	}
	return b.Bytes(), nil
}

//GobUnmarshal *
func GobUnmarshal(data []byte, v interface{}) error {
	b := bytes.NewBuffer(data)
	return gob.NewDecoder(b).Decode(v)
}

func JsonMarshal(v interface{}) ([]byte, error) {
	return json.Marshal(v)
}

func JsonMarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
	return json.MarshalIndent(v, prefix, indent)
}

func JsonMarshalToString(v interface{}) (string, error) {
	return json.MarshalToString(v)
}

func JsonUnmarshal(data []byte, v interface{}) error {
	return json.Unmarshal(data, v)
}

//GetENV *
func GetENV(key string) string {
	return strings.TrimSpace(os.Getenv(key))
}

//GetENVToBool *
func GetENVToBool(key string) bool {
	envStr := strings.TrimSpace(os.Getenv(key))
	boo, err := StringUtils(envStr).Bool()
	if nil != err {
		boo = false
	}
	return boo
}

//GetENVToInt *
func GetENVToInt(key string) (int, error) {
	envStr := strings.TrimSpace(os.Getenv(key))
	return StringUtils(envStr).Int()
}

//GetENVToInt64 *
func GetENVToInt64(key string) (int64, error) {
	envStr := strings.TrimSpace(os.Getenv(key))
	return StringUtils(envStr).Int64()
}

//ToStr *
func ToStr(value interface{}) (s string) {
	switch v := value.(type) {
	case bool:
		s = strconv.FormatBool(v)
	case float32:
		s = strconv.FormatFloat(float64(v), 'f', 2, 32)
	case float64:
		s = strconv.FormatFloat(v, 'f', 2, 64)
	case int:
		s = strconv.FormatInt(int64(v), 10)
	case int8:
		s = strconv.FormatInt(int64(v), 10)
	case int16:
		s = strconv.FormatInt(int64(v), 10)
	case int32:
		s = strconv.FormatInt(int64(v), 10)
	case int64:
		s = strconv.FormatInt(int64(v), 10)
	case uint:
		s = strconv.FormatUint(uint64(v), 10)
	case uint8:
		s = strconv.FormatUint(uint64(v), 10)
	case uint16:
		s = strconv.FormatUint(uint64(v), 10)
	case uint32:
		s = strconv.FormatUint(uint64(v), 10)
	case uint64:
		s = strconv.FormatUint(v, 10)
	case string:
		s = v
	case []byte:
		s = string(v)
	default:
		s = fmt.Sprintf("%v", v)
	}
	return s
}

//IsFileExist 文件是否存在
func IsFileExist(path string) (bool, os.FileInfo, error) {
	fileInfo, err := os.Stat(path)
	if err == nil {
		return true, fileInfo, nil
	}
	if os.IsNotExist(err) {
		return false, nil, nil
	}
	return false, nil, err
}

//IsEmpty *
func IsEmpty(value ...string) bool {
	for _, val := range value {
		if "" == strings.TrimSpace(val) {
			return true
		}
	}
	return false
}

// 判断所给路径是否为文件夹
func IsDir(path string) bool {
	s, err := os.Stat(path)
	if err != nil {
		return false
	}
	return s.IsDir()
}

//StringUtils *
type StringUtils string

//Set *
func (s *StringUtils) Set(v string) {
	if v != "" {
		*s = StringUtils(v)
	} else {
		s.Clear()
	}
}

//Clear *
func (s *StringUtils) Clear() {
	*s = StringUtils(0x1E)
}

//Exist *
func (s StringUtils) Exist() bool {
	return string(s) != string(0x1E)
}

//Bool *
func (s StringUtils) Bool() (bool, error) {
	v, err := strconv.ParseBool(s.String())
	return bool(v), err
}

//Float32 *
func (s StringUtils) Float32() (float32, error) {
	v, err := strconv.ParseFloat(s.String(), 32)
	return float32(v), err
}

//Float64 *
func (s StringUtils) Float64() (float64, error) {
	return strconv.ParseFloat(s.String(), 64)
}

//Int *
func (s StringUtils) Int() (int, error) {
	v, err := strconv.ParseInt(s.String(), 10, 32)
	return int(v), err
}

//Int8 *
func (s StringUtils) Int8() (int8, error) {
	v, err := strconv.ParseInt(s.String(), 10, 8)
	return int8(v), err
}

//Int16 *
func (s StringUtils) Int16() (int16, error) {
	v, err := strconv.ParseInt(s.String(), 10, 16)
	return int16(v), err
}

//Int32 *
func (s StringUtils) Int32() (int32, error) {
	v, err := strconv.ParseInt(s.String(), 10, 32)
	return int32(v), err
}

//Int64 *
func (s StringUtils) Int64() (int64, error) {
	v, err := strconv.ParseInt(s.String(), 10, 64)
	return int64(v), err
}

//Uint *
func (s StringUtils) Uint() (uint, error) {
	v, err := strconv.ParseUint(s.String(), 10, 32)
	return uint(v), err
}

//Uint8 *
func (s StringUtils) Uint8() (uint8, error) {
	v, err := strconv.ParseUint(s.String(), 10, 8)
	return uint8(v), err
}

//Uint16 *
func (s StringUtils) Uint16() (uint16, error) {
	v, err := strconv.ParseUint(s.String(), 10, 16)
	return uint16(v), err
}

//Uint32 *
func (s StringUtils) Uint32() (uint32, error) {
	v, err := strconv.ParseUint(s.String(), 10, 32)
	return uint32(v), err
}

//Uint64 *
func (s StringUtils) Uint64() (uint64, error) {
	v, err := strconv.ParseUint(s.String(), 10, 64)
	return uint64(v), err
}

//ToTitleLower *
func (s StringUtils) ToTitleLower() string {
	str := strings.ToLower(s.String()[:1]) + s.String()[1:]
	return str
}

//ToTitleUpper *
func (s StringUtils) ToTitleUpper() string {
	str := strings.ToUpper(s.String()[:1]) + s.String()[1:]
	return str
}

//Contains *
func (s StringUtils) Contains(sep string) bool {
	index := strings.Index(s.String(), sep)
	return index > -1
}

//String *
func (s StringUtils) String() string {
	if s.Exist() {
		return string(s)
	}
	return ""
}

//Md5 *
func (s StringUtils) Md5() string {
	m := md5.New()
	m.Write([]byte(s.String()))
	return hex.EncodeToString(m.Sum(nil))
}

//Sha1 *
func (s StringUtils) Sha1() string {
	sha := sha1.New()
	sha.Write([]byte(s.String()))
	return hex.EncodeToString(sha.Sum(nil))
}

//Sha256 *
func (s StringUtils) Sha256() string {
	sha := sha256.New()
	sha.Write([]byte(s.String()))
	return hex.EncodeToString(sha.Sum(nil))
}

//Sha512 *
func (s StringUtils) Sha512() string {
	sha := sha512.New()
	sha.Write([]byte(s.String()))
	return hex.EncodeToString(sha.Sum(nil))
}

//HmacSha1 *
func (s StringUtils) HmacSha1(key string) string {
	mc := hmac.New(sha1.New, []byte(key))
	mc.Write([]byte(s.String()))
	return hex.EncodeToString(mc.Sum(nil))
}

//HmacSha256 *
func (s StringUtils) HmacSha256(key string) string {
	mc := hmac.New(sha256.New, []byte(key))
	mc.Write([]byte(s.String()))
	return hex.EncodeToString(mc.Sum(nil))
}

//HmacSha512 *
func (s StringUtils) HmacSha512(key string) string {
	mc := hmac.New(sha512.New, []byte(key))
	mc.Write([]byte(s.String()))
	return hex.EncodeToString(mc.Sum(nil))
}

//StdBase64Encode *
func (s StringUtils) StdBase64Encode() string {
	return base64.StdEncoding.EncodeToString([]byte(s.String()))
}

//URLBase64Encode *
func (s StringUtils) URLBase64Encode() string {
	return base64.URLEncoding.EncodeToString([]byte(s.String()))
}

//Base64Decode *
func (s StringUtils) Base64Decode() (string, error) {

	inputVal := s.String()

	seg, err := url.PathUnescape(inputVal)
	if nil != err {
		return "", err
	}

	if l := len(seg) % 4; l > 0 {
		seg += strings.Repeat("=", 4-l)
	}
	v, err := base64.URLEncoding.DecodeString(seg)
	return string(v), err
}

//ReplaceSBCCommaOrSemiColonToDBC 替换字符串中的全角逗号和分号为半角逗号
func (s StringUtils) ReplaceSBCCommaOrSemiColonToDBC() string {
	inputVal := s.String()

	sourceRune := []rune(inputVal)

	for i, r := range sourceRune {
		if unicode.IsPunct(r) {
			if '，' == r || ';' == r || '；' == r {
				sourceRune[i] = ','
			}
		}
	}

	return string(sourceRune)
}

//Contains 判断 source 是否包含在target中，target支持类型:Array/Slice/Map
func Contains(source interface{}, target interface{}) (bool, error) {
	t := reflect.ValueOf(target)
	switch reflect.TypeOf(target).Kind() {
	case reflect.Slice, reflect.Array:
		for i := 0; i < t.Len(); i++ {
			if t.Index(i).Interface() == source {
				return true, nil
			}
		}
	case reflect.Map:
		if t.MapIndex(reflect.ValueOf(source)).IsValid() {
			return true, nil
		}
	}

	return false, fmt.Errorf("%s", "Invalid array")
}

//IsPhysicalInterfaceDevice 查看网卡是否是物理设备
func IsPhysicalInterfaceDevice(name string) (bool, error) {
	name = strings.TrimSpace(name)

	const (
		netDevicePath        = "/sys/class/net/"
		virtualNetDevicePath = "/devices/virtual/net/"
	)

	filePath := netDevicePath + name
	virtualFilePath := virtualNetDevicePath + name

	fileInfo, err := os.Lstat(filePath)
	if nil != err {
		return false, err
	}

	if (fileInfo.Mode() & os.ModeSymlink) > 0 {
		linkPath, err := os.Readlink(filePath)
		if err != nil {
			return false, err
		}
		if strings.LastIndex(linkPath, virtualFilePath) == -1 {
			return true, nil
		}
	}

	return false, nil
}

//GetIPs 获取当前机器IP地址,filterPhysical为true时，只获取物理络设备IP地址
func GetIPs(filterPhysical ...bool) (ipv4 []string, err error) {

	interfaces, err := net.Interfaces()
	if err != nil {
		return nil, err
	}

	physicalInterface := false
	if len(filterPhysical) > 0 {
		physicalInterface = filterPhysical[0]
	}

	var getIPs = func(inf net.Interface) {
		addrs, _ := inf.Addrs()
		for _, address := range addrs {
			if ipNet, ok := address.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
				if ipNet.IP.To4() != nil {
					ipv4 = append(ipv4, ipNet.IP.String())
				}
			}
		}
	}

	for _, inf := range interfaces {
		if (inf.Flags & net.FlagUp) != 0 {
			if physicalInterface {
				ethName := inf.Name
				pEth, err := IsPhysicalInterfaceDevice(ethName)
				if nil != err {
					return nil, err
				}
				if pEth {
					getIPs(inf)
				}
			} else {
				getIPs(inf)
			}
		}
	}
	return
}
